/*
	Copyright (c) 2008 Memorphic Ltd. All rights reserved.
	
	Redistribution and use in source and binary forms, with or without 
	modification, are permitted provided that the following conditions 
	are met:
	
		* Redistributions of source code must retain the above copyright 
		notice, this list of conditions and the following disclaimer.
	    	
	    * Redistributions in binary form must reproduce the above 
	    copyright notice, this list of conditions and the following 
	    disclaimer in the documentation and/or other materials provided 
	    with the distribution.
	    	
	    * Neither the name of MEMORPHIC LTD nor the names of its 
	    contributors may be used to endorse or promote products derived 
	    from this software without specific prior written permission.
	
	THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
	"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
	LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
	A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 
	OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 
	SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 
	LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 
	DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 
	THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 
	(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 
	OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package memorphic.slide
{
	import flash.events.EventDispatcher;
	import flash.utils.Dictionary;
	import flash.utils.getQualifiedClassName;
	
	import memorphic.core.IHistory;
	
	import mx.managers.IHistoryManagerClient;
	
	use namespace slide_internal;
	
	
	// TODO: Complete the integration with Flex history management
	[Event(name="stateChange", type="memorphic.slide.StateEvent")]
	[Event(name="beforeStateChange", type="memorphic.slide.StateEvent")]
	public class StateManager extends EventDispatcher implements IHistory,IHistoryManagerClient
	{
		
		
		private var __stateURI:String;
		private var aliasList:Object;
		private var nonHistoryList:Object;
		
		private var __homeStateURI:String;
		
		private static var instanceCount:uint = 0;
		private var myInstanceCount:uint;
		
		slide_internal var addAllStatesToHistory:Boolean = false;
		slide_internal static var instances:Dictionary = new Dictionary(true);
		
		private var uriEventMonitors:Dictionary = new Dictionary();
		
		public static var historyLength:int = 100;
		
		private var __stateHistory:Array = new Array();
		private var __historyPosition:int = -1;
		
		
		[Bindable("stateChange")]
		public function get stateHistory():Array
		{
			return __stateHistory.concat();
		}
		
		
		/**
		 * 
		 */
		[Bindable("stateChange")]
		public function get stateURI():String
		{
			return __stateURI;
		}
		public function set stateURI(uri:String):void
		{
			setStateURI(uri, false);
		}
		
		
		/**
		 * 
		 */
		protected function setStateURI(uri:String, noHistoryAppend:Boolean=false):void
		{
			if(aliasList[uri]){
				uri = aliasList[uri];
			}
			var oldState:String = __stateURI;
			__stateURI = uri;
			if(!StateMemento(saveState()).equals(currentHistoryItem)){
				dispatchEvent(new StateEvent(StateEvent.BEFORE_STATE_CHANGE, uri, oldState));
				if(!noHistoryAppend){
					appendStateToLocalHistory();
				}
				dispatchEvent(new StateEvent(StateEvent.STATE_CHANGE, uri, oldState));
			}
		}
		
		
		
		/**
		 * 
		 */
		[Bindable("stateChange")]
		public function get isAtRoot():Boolean
		{
			return stateURI.split("/").length == 1;
		}
		
		
		/**
		 * 
		 */
		[Bindable("stateChange")]
		public function get isHome():Boolean
		{
			return stateURI == homeStateURI;
		}
		
		
	
		/**
		 * 
		 */
		public function get homeStateURI():String
		{
			return __homeStateURI;
			
		}
		
		
		/**
		 * Returns true if the current state is a parent state of the state given
		 * TODO: rename / move
		 */
		public function currentStateIsParentOf(otherState:String, inclusive:Boolean=false):Boolean
		{
			return ViewStateUtil.stateIsParentOf(stateURI, otherState, inclusive);
		}
		
		
		/**
		 * Returns true if the current state is a child state of the state given
		 * TODO: rename / move
		 */
		public function currentStateIsChildOf(otherState:String, inclusive:Boolean=false):Boolean
		{
			return ViewStateUtil.stateIsParentOf(otherState, stateURI, inclusive);
		}
		
		

		
		/**
		 * 
		 * @param homeState If omitted, will be the same as initialState
		 */
		public function StateManager(initialState:String="", homeState:String=null)
		{
			instances[this] = 1;
			myInstanceCount = instanceCount++;
			
			aliasList = new Object();
			nonHistoryList = new Object();
			if(homeState == null){
				homeState = initialState;
			}
			__homeStateURI = homeState;
			stateURI = initialState;
		}
		
		
		
		public function getURIStateMonitor(uri:String):URIStateMonitor
		{
			var monitor:URIStateMonitor = uriEventMonitors[uri];
			if(!monitor){
				monitor = uriEventMonitors[uri] = new URIStateMonitor(uri);
				monitor.initStateManager(this);
			}
			return monitor;
		}
		
		
		
		/**
		 * Defines an alias for a state uri. It's a bit like a URL redirect
		 * 
		 * For example, if you use:
		 * 
		 * 	addStateAlias("app/content/home", "home");
		 * 
		 * then setting stateURI to "home" will actually set it to "app/content/home".
		 */ 
//		public function addStateAlias(primary:String, alias:String):void
//		{
//			aliasList[alias] = primary;
//		}
		
		
		
		/**
		 * Prevents a state from being added to the state history
		 * 
		 */
		public function addNonHistoryState(uri:String):void
		{
			nonHistoryList[uri] = true;
			// it's possible that the app has already been in this state, so remove it from the history now.
			var n:int = __stateHistory.length;
			while(n--){
				if(StateMemento(__stateHistory[n]).viewStateURI == uri){
					__stateHistory.splice(n, 1);
					if(n <= __historyPosition){
						__historyPosition--;
					}
				}
			}
		}
		
		
		/**
		 * Set the application state to be up one level.
		 * 
		 * For example, if the current state is "a/b/c", the new state would be "a/b"
		 */ 
		public function goUp():void
		{
			var uri:String = stateURI;
			var upURI:String = uri.substr(0, uri.lastIndexOf("/"));
			stateURI = upURI;
		}
		
		
		public function goHome():void
		{
			stateURI = __homeStateURI;
		}
		

/////////////////////////////////////////////////////////////////
//
//		IHistory interface
//	
		[Bindable("stateChange")]
		public function get canGoForward():Boolean
		{
			return __historyPosition +1 < __stateHistory.length;
		}
		
		[Bindable("stateChange")]
		public function get canGoBack():Boolean
		{
			return __historyPosition > -1;
		}
		
		public function back():void
		{
			go(-1);
		}
		
		public function forward():void
		{
			go(1);
		}
		
		public function go(offset:int):void
		{
			if(offset == 0) return;
			var newPosition:int = __historyPosition + offset;
			if(newPosition < __stateHistory.length && newPosition > -1){
				// setStateURI(StateHistoryItem(__stateHistory[__historyPosition]).stateURI, true);
				loadState(__stateHistory[newPosition]);
				__historyPosition = newPosition;
			}
			
		}
		
		[Bindable("stateChange")]
		public function get historyPosition():uint
		{
			return __historyPosition;
		}
		
		[Bindable("stateChange")]
		public function get historyLength():uint
		{
			return __stateHistory.length;
		}
		
		// not in the interface, but related
		public function get currentHistoryItem():StateMemento
		{
			if(historyPosition < 0){
				return null;
			}
			return StateMemento(__stateHistory[historyPosition]);
		}
		
/////////////////////////////////////////////////////////////////
//
//		IHistoryManagerClient interface
//
		/**
		 * Override createHistoryItem(), saveState() and loadState() in subclasses, to add better History-management 
		 * support that is more specific to your application
		 */
		public function saveState():Object
		{
			return createHistoryItem();
		}
		
		/**
		 * Override createHistoryItem(), saveState() and loadState() in subclasses, to add better History-management 
		 * support that is more specific to your application
		 */
		public function loadState(state:Object):void
		{
			// stateURI = (state as StateHistoryItem).stateURI;
			setStateURI((state as StateMemento).viewStateURI, true);
		}
		
		
		public override function toString():String
		{
			return getQualifiedClassName(this) + myInstanceCount;
		}
		
		
		protected function appendStateToLocalHistory():void
		{
			if(!addAllStatesToHistory && nonHistoryList[stateURI]){
				return;
			}
			
			if(__stateHistory.length > __historyPosition+1){
				__stateHistory.splice(__historyPosition+1, __stateHistory.length-__historyPosition);
			}
			__stateHistory.push(createHistoryItem());
			if(__stateHistory.length > historyLength){
				__stateHistory.shift();
			}
			__historyPosition = __stateHistory.length - 1;
		}
		
		
		protected function createHistoryItem():StateMemento
		{
			return new StateMemento(stateURI);
		}
		
	}
	
	
	
}